import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import models from '../assets/models';
import { SimObject } from './simulation';

const baseUrl = "./";

export class AssetManager {

    textureLoader = new THREE.TextureLoader();

    gltfLoader = new GLTFLoader();

    textures = {
        'base': this.loadTexture(`${baseUrl}textures/base.png`),
        'specular': this.loadTexture(`${baseUrl}textures/specular.png`),
        'grid': this.loadTexture(`${baseUrl}textures/grid.png`),

    };

    statusIcons = {
        'no-power': this.loadTexture(`${baseUrl}statusIcons/no-power.png`, true),
        'no-road-access': this.loadTexture(`${baseUrl}statusIcons/no-road-access.png`, true)
    };

    models: { [key: string]: THREE.Group; } = {};

    sprites = {};

    modelCount: number;

    loadedModelCount: number;

    onLoad: () => void;

    constructor() {

        this.modelCount = Object.keys(models).length;
        this.loadedModelCount = 0;

        for (const [name, meta] of Object.entries(models)) {
            this.loadModel(name, meta);
        }

    }

    getModel(name: string, simObject: SimObject, transparent = false): THREE.Group {
        const model = this.models[name].clone();

        model.traverse((obj) => {
            obj.userData.simObject = simObject;
            if (obj instanceof THREE.Mesh) {
                obj.material = obj.material.clone();
                obj.material.transparent = transparent;
            }
        });

        return model;
    }

    private loadTexture(url: string, flipY = false) {
        const texture = this.textureLoader.load(url);
        texture.colorSpace = THREE.SRGBColorSpace;
        texture.flipY = flipY;
        return texture;
    }

    private loadModel(name: string, { filename, scale = 1, rotation = 0, receiveShadow = true, castShadow = true }) {
        this.gltfLoader.load(`${baseUrl}models/${filename}`, gltf => {

            const model = gltf.scene;
            model.name = filename;
            model.traverse((obj) => {

                if (obj instanceof THREE.Mesh) {
                    obj.material = new THREE.MeshLambertMaterial({
                        map: this.textures.base,
                        specularMap: this.textures.specular
                    });
                    obj.receiveShadow = receiveShadow;
                    obj.castShadow = castShadow;
                }
            });

            model.rotation.set(0, THREE.MathUtils.degToRad(rotation), 0);
            model.scale.set(scale / 30, scale / 30, scale / 30);

            this.models[name] = model;

            // Once all models are loaded
            this.loadedModelCount++;
            if (this.loadedModelCount == this.modelCount && this.onLoad) {
                this.onLoad();
            }
        });
    }
}

export const assetManager = new AssetManager();